# -*- coding: utf-8 -*-
#--------------------------------------------------------------------------
# Copyright (c) 2009 Barry Schwartz
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

#--------------------------------------------------------------------------
#
# Cython interface to libgcrypt
#

cimport python_exc

cdef class encrypter_base:

    cdef gcry_cipher_hd_t handle

    def __cinit__(self, algorithm, mode, flags, key = None, init_vector = None):
        cdef gcry_error_t errcode
        errcode = gcry_cipher_open(&self.handle, algorithm, mode, flags)
        if errcode != 0:
            python_exc.PyErr_NoMemory() # FIX: Is this the right exception?
        if key != None:
            self.set_key(key)
        if init_vector != None:
            self.set_init_vector(init_vector)

    def __dealloc__(self):
        gcry_cipher_close(self.handle)

    def set_key(self, k):
        assert isinstance(k, bytes)
        cdef gcry_error_t errcode
        errcode = gcry_cipher_setkey(self.handle, <unsigned char *> k, len(k))
        if errcode != 0:
            raise ValueError(gcry_strerror(errcode).decode())

    def set_init_vector(self, v):
        assert isinstance(v, bytes)
        cdef gcry_error_t errcode
        errcode = gcry_cipher_setiv(self.handle, <unsigned char *> v, len(v))
        if errcode != 0:
            raise ValueError(gcry_strerror(errcode).decode())

    def encrypt(self, data):
        assert isinstance(data, bytes)
        cdef gcry_error_t errcode
        encrypted_data = b''.ljust(len(data))
        errcode = gcry_cipher_encrypt(self.handle,
                                      <unsigned char *> encrypted_data, len(encrypted_data),
                                      <unsigned char *> data, len(data))
        if errcode != 0:
            raise ValueError(gcry_strerror(errcode).decode())
        return encrypted_data

cpdef arcfour_encrypter(key = None):
    return encrypter_base(GCRY_CIPHER_ARCFOUR, GCRY_CIPHER_MODE_STREAM, 0,
                          key = key)

cpdef aes_encrypter(key = None, init_vector = None):
    return encrypter_base(GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CBC, 0,
                          key = key, init_vector = init_vector)

def encrypt_by_arcfour(data, key):
    return arcfour_encrypter(key).encrypt(data)

def encrypt_by_aes(data, key, init_vector):
    return aes_encrypter(key, init_vector).encrypt(data)

#--------------------------------------------------------------------------

def md5_digest(data):
    assert isinstance(data, bytes)
    digest = b''.ljust(16)
    gcry_md_hash_buffer(GCRY_MD_MD5, <unsigned char *> digest,
                        <unsigned char *> data, len(data))
    return digest

#--------------------------------------------------------------------------

# Initialize gcrypt.
gcry_check_version(NULL)
gcry_control(GCRYCTL_DISABLE_SECMEM, 0)
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0)

#--------------------------------------------------------------------------
